﻿using YNTK.Common.Configuration;
using YNTK.Common.Const;
using YNTK.Common.Core.Captcha.General;
using YNTK.Common.Core.Manager;
using YNTK.Common.Enum;
using YNTK.Common.Helper;
using YNTK.DataEncryption;
using YNTK.Dependency;
using YNTK.DynamicApiController;
using YNTK.FriendlyException;
using YNTK.JsonSerialization;
using YNTK.RemoteRequest.Extensions;
using YNTK.System.Interfaces.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using OnceMi.AspNetCore.OSS;
using System;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Yitter.IdGenerator;
using YNTK.Common.Filter;
using YNTK.System.Entitys.Dto.Common;
using System.Collections.Generic;
using Polly;
using System.Web;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;

namespace YNTK.System.Service.Common
{
    /// <summary>
    /// 业务实现：通用控制器
    /// </summary>
    [ApiDescriptionSettings(Tag = "文件服务", Name = "File", Order = 800)]
    [Route("api/[controller]")]
    public class FileService : IFileService, IDynamicApiController, ITransient
    {
        private readonly IGeneralCaptcha _captchaHandle;// 验证码服务
        private readonly IConfiguration _configuration;
        private readonly IUserManager _userManager;
        private readonly IOSSServiceFactory _oSSServiceFactory;
        private const long maxFileSize = 1024 * 1024 * 1024;//目前限制文件上传1GB以内，单位bytes
        private readonly HttpContext _httpContext;

        /// <summary>
        /// 初始化一个<see cref="FileService"/>类型的新实例
        /// </summary>
        public FileService(IGeneralCaptcha captchaHandle, IConfiguration configuration, IUserManager userManager, IOSSServiceFactory oSSServiceFactory)
        {
            _captchaHandle = captchaHandle;
            _configuration = configuration;
            _userManager = userManager;
            _oSSServiceFactory = oSSServiceFactory;
            _httpContext = App.HttpContext;
        }

        /// <summary>
        /// 上传文件/图片
        /// </summary>
        /// <returns></returns>
        [HttpPost("Uploader/{type}")]
        [AllowAnonymous]
        [NonAction]
        public async Task<dynamic> Uploader(string type, IFormFile file)
        {
            var fileType = Path.GetExtension(file.FileName).Replace(".", "");
            if (!this.AllowFileType(fileType, type))
                throw YNTKException.Oh(ErrorCode.D1800);
            var _filePath = GetPathByType(type);
            var _fileName = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + Path.GetExtension(file.FileName);
            await UploadFileByType(file, _filePath, _fileName);
            return new { name = _fileName, url = string.Format("/api/File/Image/{0}/{1}", type, _fileName) };
        }

        /// <summary>
        /// 生成图片链接
        /// </summary>
        /// <param name="type">图片类型 </param>
        /// <param name="fileName">注意 后缀名前端故意把 .替换@ </param>
        /// <returns></returns>
        [HttpGet("Image/{type}/{fileName}")]
        [AllowAnonymous]
        [NonAction]
        public async Task<IActionResult> GetImg(string type, string fileName)
        {
            var filePath = Path.Combine(GetPathByType(type), fileName.Replace("@", "."));
            return await DownloadFileByType(filePath, fileName);
            //return new FileStreamResult(new FileStream(filePath, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName };
        }

        /// <summary>
        /// 生成大屏图片链接
        /// </summary>
        /// <param name="type">图片类型 </param>
        /// <param name="fileName">注意 后缀名前端故意把 .替换@ </param>
        /// <returns></returns>
        [HttpGet("VisusalImg/{type}/{fileName}")]
        [AllowAnonymous]
        [NonAction]
        public async Task<IActionResult> GetScreenImg(string type, string fileName)
        {
            var filePath = Path.Combine(GetPathByType(type), type, fileName.Replace("@", "."));
            return await DownloadFileByType(filePath, fileName);
            //return new FileStreamResult(new FileStream(filePath, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName };
        }

        #region 验证码
        /// <summary>
        /// 获取图形验证码
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns></returns>
        [HttpGet("ImageCode/{timestamp}")]
        [AllowAnonymous]
        [NonUnify]
        [NonAction]
        public IActionResult GetCode(string timestamp)
        {
            return new FileContentResult(_captchaHandle.CreateCaptchaImage(timestamp, 114, 32), "image/jpeg");
        }

        /// <summary>
        /// 获取简洁漂亮的图形验证码
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns></returns>
        [HttpGet("ValidateCode/{timestamp}")]
        [AllowAnonymous]
        [NonUnify]

        public async Task<IActionResult> GetValidateCode(string timestamp)
        {
            (byte[] imgBytes, string code) = await _captchaHandle.CreateValidateGraphic(timestamp, 4, 114, 32, 20);
            return new FileContentResult(imgBytes, "image/jpeg");
        }

        /// <summary>
        /// 获取加减乘除计算验证码
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns></returns>
        [HttpGet("ValidateGraphic/{timestamp}")]
        [AllowAnonymous]
        [NonUnify]
        [NonAction]
        public async Task<IActionResult> GetValidate(string timestamp)
        {
            (byte[] imgBytes, string code) = await _captchaHandle.CreateValidateGraphic(timestamp);
            return new FileContentResult(imgBytes, "image/jpeg");
        }
        #endregion

        /// <summary>
        /// 上传图片
        /// </summary>
        /// <returns></returns>
        [HttpPost("uploader/userAvatar")]
        [AllowAnonymous]
        [NonAction]
        public async Task<dynamic> UploadImage(IFormFile file)
        {
            var ImgType = Path.GetExtension(file.FileName).Replace(".", "");
            if (!this.AllowImageType(ImgType))
                throw YNTKException.Oh(ErrorCode.D5013);
            var filePath = FileVariable.UserAvatarFilePath;
            var fileName = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + Path.GetExtension(file.FileName);
            await UploadFileByType(file, filePath, fileName);
            return new { name = fileName, url = "/api/file/Image/UserAvatar/" + fileName };
        }

        #region 文件上传
        /// <summary>
        /// 通用单文件上传，并返回文件上传成功后的信息，1GB以内文件上传使用
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        [HttpPost("upload/singlefile")]
        [RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
        public async Task<TkResult<ReturnUploadFileDto>> UploadFile(IFormFile file)
        {
            try
            {
                var user = _httpContext.User.FindFirst(ClaimConst.CLAINM_USERID);
                string userId = user.Value;
                string organizeId = user.Subject.FindFirst(ClaimConst.ORGANIZEID).Value;
                string mechanismId = user.Subject.FindFirst(ClaimConst.MECHANISMID).Value;
                string roleId = user.Subject.FindFirst(ClaimConst.ROLEID).Value;
                //账号类型【0.平台运营人员 1.超级管理员 2.组织管理员 3.组织用户 4.第三方登录用户 5.游客用户】
                var authenticationType = user.Subject.FindFirst(ClaimConst.ACCOUNTTYPE).Value;
                //if(authenticationType=="4" || authenticationType=="5")
                //    throw YNTKException.Oh(ErrorCode.D1016);

                //判断是否选择文件
                if (file == null) throw YNTKException.Oh(ErrorCode.D1802);
                //判断文件大小（单位：字节）
                long size = file.Length;
                if (size > maxFileSize) //100MB = 1024 * 1024 * 100
                    throw YNTKException.Oh("目前最大允许上传文件为1GB！");
                //文件的原始名称
                string fileOriginName = file.FileName;
                //文件存储相对目录
                string filePath = $"{DateTime.Now.Year}/{authenticationType}/{organizeId}/{mechanismId}/{userId}/";
                //生成文件的名称
                string extension = Path.GetExtension(fileOriginName);//获取文件的源后缀
                if (string.IsNullOrEmpty(extension)) throw YNTKException.Oh(ErrorCode.D1803);
                if (!AllowFileType(extension.Replace(".", ""), "file")) throw YNTKException.Oh(ErrorCode.D1804);
                //生成新的文件名
                string fileId = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString();
                string fileName = fileId + "{@yntk@}" + fileOriginName;
                await UploadFileByType(file, FileVariable.OrganizationFilePath + filePath, fileName);
                var result = new ReturnUploadFileDto()
                {
                    fileId = fileId,//文件id
                    fileName = fileOriginName,//文件名称
                    saveFileName = fileName,//存储文件名
                    extension = extension,//文件后缀
                    size = size,//文件大小
                    fileSize = FileHelper.ToFileSize(size),//文件大小
                    fileDirectory = filePath,//文件存储目录
                    storagePath= filePath+ fileName//文件存储路径,存储目录/存储文件名
                };
                return TkResult<ReturnUploadFileDto>.Success(result);
            }
            catch (Exception ex)
            {
                if(ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh(ErrorCode.D8001);
            }
        }

        /// <summary>
        ///  多文件上传
        /// </summary>
        /// <param name="formCollection">表单集合值</param>
        /// <returns>服务器存储的文件信息</returns>
        [HttpPost("upload/multifile")]
        [RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
        public async Task<TkResult<List<ReturnUploadFileDto>>> MultiFileUpload(IFormCollection formCollection)
        {
            var user = _httpContext.User.FindFirst(ClaimConst.CLAINM_USERID);
            string userId = user.Value;
            string organizeId = user.Subject.FindFirst(ClaimConst.ORGANIZEID).Value;
            string mechanismId = user.Subject.FindFirst(ClaimConst.MECHANISMID).Value;
            string roleId = user.Subject.FindFirst(ClaimConst.ROLEID).Value;
            //账号类型【0.平台运营人员 1.超级管理员 2.组织管理员 3.组织用户 4.第三方登录用户 5.游客用户】
            var authenticationType = user.Subject.FindFirst(ClaimConst.ACCOUNTTYPE).Value;
            //if (authenticationType == "4" || authenticationType == "5")
            //    throw YNTKException.Oh(ErrorCode.D1016);

            var result = new List<ReturnUploadFileDto>();
            //文件存储相对目录
            string filePath = $"{DateTime.Now.Year}/{authenticationType}/{organizeId}/{mechanismId}/{userId}/";
            try
            {            
                //FormCollection转化为FormFileCollection
                var files = (FormFileCollection)formCollection.Files;
                if (files.Any())
                {
                    if (files.Sum(f=>f.Length) > maxFileSize) //100MB = 1024 * 1024 * 100
                        throw YNTKException.Oh("目前最大允许上传文件为1GB！");
                    string fileOriginName = "";//文件的原始名称
                    string extension = "";//文件后缀
                    string fileId = "";//文件id
                    string fileName = "";//文件名
                    long size=0;//文件大小
                    foreach (var file in files)
                    {
                        fileOriginName = file.FileName;
                        extension = Path.GetExtension(fileOriginName);//获取文件格式，拓展名
                        if (string.IsNullOrEmpty(extension))
                            return TkResult<List<ReturnUploadFileDto>>.Fail(result, $"文件“{fileOriginName}”没有后缀！");
                        if (!AllowFileType(extension.Replace(".", ""), "file"))
                            return TkResult<List<ReturnUploadFileDto>>.Fail(result, $"文件{fileOriginName}格式不允许上传！");
                        fileId = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString();
                        fileName = fileId + "{@yntk@}" + fileOriginName;
                        size = file.Length;
                        //文件保存
                        await UploadFileByType(file, FileVariable.OrganizationFilePath + filePath, fileName);
                        result.Add(new ReturnUploadFileDto()
                        {
                            fileId = fileId,//文件id
                            fileName = fileOriginName,//文件名称
                            saveFileName = fileName,//存储文件名
                            extension = extension,//文件后缀
                            size = size,//文件大小
                            fileSize = FileHelper.ToFileSize(size),//文件大小
                            fileDirectory = filePath,//文件存储目录
                            storagePath = filePath + fileName//文件存储路径,存储目录/存储文件名
                        });
                    }
                }
                else throw YNTKException.Oh(ErrorCode.D1806);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh(ErrorCode.D8001);
            }
            if (result.Any()) return TkResult<List<ReturnUploadFileDto>>.Success(result);
            else throw YNTKException.Oh(ErrorCode.D1806);
        }

        /// <summary>
        /// 大文件分片上传，100MB以上文件请尽量使用分片上传，单片文件请控制在100MB以内
        /// </summary>
        /// <param name="file">当前分片文件</param>
        /// <param name="slice">分片文件信息请求模型</param>
        /// <returns></returns>
        [HttpPost("upload/ruleuploadfile")]
        public async Task<TkResult<SliceFileResponseDto>> RuleUploadFile
            (IFormFile file,[FromQuery] SliceFileRequestModel slice)
        {
            var user = _httpContext.User.FindFirst(ClaimConst.CLAINM_USERID);
            string userId = user.Value;
            string organizeId = user.Subject.FindFirst(ClaimConst.ORGANIZEID).Value;
            string mechanismId = user.Subject.FindFirst(ClaimConst.MECHANISMID).Value;
            string roleId = user.Subject.FindFirst(ClaimConst.ROLEID).Value;
            //账号类型【0.平台运营人员 1.超级管理员 2.组织管理员 3.组织用户 4.第三方登录用户 5.游客用户】
            var authenticationType = user.Subject.FindFirst(ClaimConst.ACCOUNTTYPE).Value;
            //if (authenticationType == "4" || authenticationType == "5")
            //    throw YNTKException.Oh(ErrorCode.D1016);
            if (file == null) throw YNTKException.Oh(ErrorCode.D1802);
            var result = new SliceFileResponseDto();
            try
            {
                result.fileName = file.FileName;//文件的原始名称
                //合并后文件存储目录
                result.fileDirectory = string.IsNullOrWhiteSpace(slice.fileDirectory) ?
                $"{DateTime.Now.Year}/{authenticationType}/{organizeId}/{mechanismId}/{userId}/" : slice.fileDirectory;
                //分片文件存储相对目录
                result.storageDirectory = string.IsNullOrWhiteSpace(slice.storageDirectory) ? 
               $"{result.fileDirectory}{result.fileName.Split('.')[0]}/" : slice.storageDirectory;
                result.extension = Path.GetExtension(result.fileName);//获取文件格式，拓展名
                //合并后文件id
                result.fileId = string.IsNullOrWhiteSpace(slice.fileId) ? 
                DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() : slice.fileId;
                //合并后存储文件名
                result.saveFileName = string.IsNullOrWhiteSpace(slice.saveFileName) ?
                result.fileId + "{@yntk@}" + result.fileName : slice.saveFileName;
                //合并文件存储路径,存储目录/存储文件名
                result.storagePath = $"{result.fileDirectory}{result.saveFileName}";
                result.number = slice.number;//当前分块序号
                result.count = slice.count;//所有块数
                //当前分片文件名称
                result.name = result.fileName + "^" + slice.number;//当前分片文件名称

                //分片文件存储目录
                string path = FileVariable.OrganizationFilePath + result.storageDirectory;
                //保存当前分片文件
                await UploadFileByType(file, path, result.name);
                //判断当前分片是不是最后一片
                var filesList = Directory.GetFiles(Path.GetDirectoryName(path));
                if (filesList.Length == slice.count)
                    result = await MergeFileAsync(result, filesList);
                else
                {
                    result.completed = false;//分片上传是否完成
                    result.size = 0;//合并文件大小
                    result.fileSize = "0b";//合并文件大小以B，KB，GB，TB形式显示
                }
                return TkResult<SliceFileResponseDto>.Success(result);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh($"[大文件分片上传]大文件分片上传失败！发生异常：{ex.Message}");
            }
        }

        /// <summary>
        /// 合并文件
        /// </summary>
        /// <param name="result">分片文件上传响应模型</param>
        /// <param name="files">分片文件</param>
        /// <returns></returns>
        private async Task<SliceFileResponseDto> MergeFileAsync
            (SliceFileResponseDto result, string[] files)
        {
            try
            {
                if (files.Length != result.count)
                    throw YNTKException.Oh("[合并文件]分片文件存储目录下的文件数量与分片数量不符合并文件失败！");
                //合并后的文件绝对地址
                string baseFileName =$"{FileVariable.OrganizationFilePath}{result.storagePath}";
                var lstFile = (from file in files select new FileSort() 
                { name=file,number= Convert.ToInt32(file.Substring(file.IndexOf('^') + 1))}).ToList();
                //排一下序，保证从0-N Write
                lstFile = lstFile.OrderBy(x => x.number).ToList();
                using (var fileStream = new FileStream(baseFileName, FileMode.Create))
                {
                    await Policy.Handle<IOException>()
                    .RetryForeverAsync()
                    .ExecuteAsync(async () =>
                    {
                        foreach (var fileSort in lstFile)
                        {
                            using (FileStream fileChunk = new FileStream(fileSort.name, FileMode.Open))
                            {
                                await fileChunk.CopyToAsync(fileStream);
                            }
                        }
                    });
                    //合并文件大小
                    result.size = fileStream.Length;
                    //合并文件大小以B，KB，GB，TB形式显示
                    result.fileSize = FileHelper.ToFileSize(result.size);
                }
                //删除分片文件
                foreach (var file in files)
                {
                    File.Delete(file);
                }
                //删除分片文件存储目录
                string path = $"{FileVariable.OrganizationFilePath}{result.storageDirectory}";
                if (!Directory.Exists(path)) Directory.Delete(path);
                result.completed = true;//分片上传是否完成
                return result;
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"[合并文件]大文件分片上传完成，合并文件发生异常：{ex.Message}");
            }
        }

        /// <summary>
        /// 手写签名Base64转换图片
        /// </summary>
        /// <param name="strbase64"></param>
        /// <returns></returns>
        [NonAction]
        public async Task<ReturnUploadFileDto> Base64StringToImage(string strbase64)
        {            
            try
            {
                var user = _httpContext.User.FindFirst(ClaimConst.CLAINM_USERID);
                string userId = user.Value;
                string organizeId = user.Subject.FindFirst(ClaimConst.ORGANIZEID).Value;
                string mechanismId = user.Subject.FindFirst(ClaimConst.MECHANISMID).Value;
                //账号类型【0.平台运营人员 1.超级管理员 2.组织管理员 3.组织用户 4.第三方登录用户 5.游客用户】
                var authenticationType = user.Subject.FindFirst(ClaimConst.ACCOUNTTYPE).Value;
                if (authenticationType == "4" || authenticationType == "5")
                    throw YNTKException.Oh(ErrorCode.D1016);

                string path = FileVariable.SignatureImageFilePath;
                //文件存储相对目录
                string filePath = $"{authenticationType}/{organizeId}/{mechanismId}/{userId}/";
                //base64的格式为  
                string typeimg = strbase64.Split(';')[0].Split('/')[1];
                strbase64 = strbase64.Replace("data:image/" + typeimg + ";base64,", string.Empty);
                //生成新的文件名
                string fileId = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString();
                string fileName = $"{fileId}@yntk@{userId}.{typeimg}";
                byte[] arr = Convert.FromBase64String(strbase64);
                MemoryStream ms = new MemoryStream(arr);
                long size = ms.Length;
                if (!Directory.Exists(path + filePath)) Directory.CreateDirectory(path + filePath);
                await File.WriteAllBytesAsync($"{path}{filePath}{fileName}", arr);
                ms.Close();
                ms.Dispose();
                var result = new ReturnUploadFileDto()
                {
                    fileId = fileId,//文件id
                    fileName = $"{userId}.{typeimg}",//文件名称
                    saveFileName = fileName,//存储文件名
                    extension = typeimg,//文件后缀
                    size = size,//文件大小
                    fileSize = FileHelper.ToFileSize(size),//文件大小
                    fileDirectory = filePath,//文件存储目录
                    storagePath = filePath + fileName//文件存储路径,存储目录/存储文件名
                };
                return result;
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh($"手写签名Base64转换图片发生异常：{ex.Message}");
            }
        }
        #endregion

        #region 下载附件
        /// <summary>
        /// 获取图片缩略图
        /// </summary>
        /// <param name="request">获取缩略图请求模型</param>
        /// <returns></returns>
        [HttpPost("get/thumbnail")]
        public async Task<FileStreamResult> GetThumbnail(GetThumbnailRequestModel request)
        {
            string year = request.storagePath.Split('/').First();
            if (year.Length == 1) request.storagePath = $"{DateTime.Now.Year}/{request.storagePath}";
            string filePath = FileVariable.OrganizationFilePath + request.storagePath;
            if (!File.Exists(filePath)) throw YNTKException.Oh(ErrorCode.D8000);
            //先求出最后出现这个字符的下标
            int index = request.storagePath.LastIndexOf('/');
            //从下一个索引开始截取
            string saveFileName = filePath.Substring(index + 1);
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
            string extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
            string fileName = Path.GetFileName(filePath);
            string thumbnailDownloadName = fileName.Split("{@yntk@}")[1];
            //缩略图路径
            string thumbnailRoot = Path.GetDirectoryName(filePath);
            string thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
            if (string.IsNullOrWhiteSpace(extension) || !AllowImageType(extension.Replace(".", "")))
                throw YNTKException.Oh("未识别文件");
            try
            {
                if (!Directory.Exists(thumbnailRoot)) Directory.CreateDirectory(thumbnailRoot);
                if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                //生成高质量缩略图
                MakeThumbnail2(filePath, thumbnailPath,request.width, request.height);
                return await DownloadFileByType(thumbnailPath, thumbnailDownloadName);
                //var result = new DownloadFileDto()
                //{
                //    fileStream = file.FileStream,
                //    contentType = file.ContentType,
                //    fileDownloadName = file.FileDownloadName,
                //    storagePath = thumbnailPath
                //};
                //return TkResult<DownloadFileDto>.Success(result);
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"获取图片缩略图异常：{ex.Message}");
            }
        }

        /// <summary>
        /// 通用单文件下载
        /// </summary>
        /// <param name="fileId">文件id</param>
        /// <param name="saveFileName">存储文件名</param>
        /// <param name="fileDirectory">文件存储目录</param>
        /// <returns></returns>
        [HttpPost("download")]
        public async Task<FileStreamResult> DownloadFile(string fileId, string saveFileName, string fileDirectory)
        {       
            if (string.IsNullOrWhiteSpace(fileId) || string.IsNullOrWhiteSpace(saveFileName)
                || string.IsNullOrWhiteSpace(fileDirectory))
                throw YNTKException.Oh("请求参数错误！");
            string year = fileDirectory.Split('/').First();
            if (year.Length == 1) fileDirectory = $"{DateTime.Now.Year}/{fileDirectory}";
            string filePath = FileVariable.OrganizationFilePath + fileDirectory + saveFileName;
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
            return await DownloadFileByType(filePath, fileDownloadName);
            //return TkResult<DownloadFileDto>.Success(new DownloadFileDto()
            //{
            //    fileStream = file.FileStream,
            //    contentType = file.ContentType,
            //    fileDownloadName = file.FileDownloadName
            //});
        }

        /// <summary>
        /// 文件下载,断点续传
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpPost("breakpoint/resume")]
        public async Task<FileStreamResult> Download(string storagePath)
        {
            string year = storagePath.Split('/').First();
            if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
            string filePath = FileVariable.OrganizationFilePath + storagePath;
            if (!File.Exists(filePath)) throw YNTKException.Oh("客户端下载文件失败,文件不存在！");
            //先求出最后出现这个字符的下标
            int index = storagePath.LastIndexOf('/');
            //从下一个索引开始截取
            string saveFileName = filePath.Substring(index + 1);
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];

            try
            {
                //异步读取文件
                return await DownloadFileByType(filePath, fileDownloadName, 4096,true);
                //var result = new DownloadFileDto()
                //{
                //    fileStream = file.FileStream,
                //    contentType = file.ContentType,
                //    fileDownloadName = file.FileDownloadName,
                //    breakpointResume = true//为true时，支持断点续传
                //};
                //return TkResult<DownloadFileDto>.Success(result);
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"客户端下载文件出现异常：{ex.Message}");
            }
        }

        /// <summary>
        /// 文件中转下载,用于下载大文件
        /// .NET Core 写Response.Body下载（取消下载服务端知道）
        /// 用于监听客户端是否保存文件失败或者取消下载了，缺点是：需要自己写断点续传逻辑......
        /// 1.平台的部分资源下载需要耗费平台流量，平台流量是需要充值购买的
        /// 2.要支持断点续传，客户端可能下载一半出现中断
        /// 3.下载过程中，客户端保存失败或者取消下载，不应该扣除用户整个文件的流量大小
        /// （如：文件100M，下载1M的时候客户端就出错了，这时只能扣除1M流量）
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpPost("download/bigfile")]
        public async Task DownloadBigFile(string storagePath)
        {
            string year = storagePath.Split('/').First();
            if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
            string filePath = FileVariable.OrganizationFilePath + storagePath;
            if (!File.Exists(filePath)) throw YNTKException.Oh("客户端下载文件失败,文件不存在！");
            //先求出最后出现这个字符的下标
            int index = storagePath.LastIndexOf('/');
            //从下一个索引开始截取
            string saveFileName = filePath.Substring(index + 1);
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];

            try
            {
                long beginPosition = 0;
                //异步读取文件
                var fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, true);
                //断点续传
                //range 参数格式：byte=1024-,这个是http协议的格式，也可以自定义格式
                string rangeStr = _httpContext.Request.Headers["range"];

                if (!string.IsNullOrEmpty(rangeStr))//断点续传
                {
                    string byteStr = rangeStr.Split("=")?[1];
                    if (!string.IsNullOrEmpty(byteStr))
                    {
                        var byteArr = byteStr.Split("-");
                        if (byteArr != null && byteArr.Length > 1)
                        {
                            beginPosition = Convert.ToInt64(byteArr[0]);
                        }
                    }
                }

                _httpContext.Response.ContentType = "application/octet-stream";
                _httpContext.Response.Headers.Append("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileDownloadName));
                _httpContext.Response.Headers.Append("Charset", "utf-8");
                _httpContext.Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
                int bufferSize = 1024;//每次读取1MB到服务器内存

                using (_httpContext.Response.Body)
                {
                    long contentLength = fs.Length;
                    _httpContext.Response.ContentLength = contentLength;
                    byte[] buffer;
                    long hasRead = 0;
                    while (hasRead < contentLength)
                    {
                        if (_httpContext.RequestAborted.IsCancellationRequested)
                        {
                            //取消下载会进来，这里可以做一些操作。。。。。
                            break;
                        }

                        fs.Seek(hasRead, SeekOrigin.Begin);
                        buffer = new byte[bufferSize];
                        //从下载文件中读取bufferSize(1024字节)大小的内容到服务器内存中
                        int currentRead = fs.Read(buffer, 0, bufferSize);
                        await _httpContext.Response.Body.WriteAsync(buffer, 0, currentRead);
                        await _httpContext.Response.Body.FlushAsync();
                        hasRead += currentRead;
                    }

                    if (hasRead == contentLength)//下载完成
                    {
                        //下载完成之后要做的事。。。。
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"客户端下载文件出现异常：{ex.Message}");
            }
        }

        /// <summary>
        /// 根据文件存储路径下载文件
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpGet("download/file/{storagePath}")]
        public async Task<FileStreamResult> GetFilePath([FromBody] string storagePath)
        {
            string year = storagePath.Split('/').First();
            if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
            string filePath = FileVariable.OrganizationFilePath + storagePath;
            //先求出最后出现这个字符的下标
            int index = storagePath.LastIndexOf('/');
            //从下一个索引开始截取
            string saveFileName = filePath.Substring(index + 1);
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
            return await DownloadFileByType(filePath, fileDownloadName);
            //return TkResult<DownloadFileDto>.Success(new DownloadFileDto()
            //{
            //    fileStream = file.FileStream,
            //    contentType = file.ContentType,
            //    fileDownloadName = file.FileDownloadName
            //});
        }

        /// <summary>
        /// 断点续传,下载文件,获取文件流,返回结果是dynamic
        /// </summary>
        /// <param name="storagePath"></param>
        /// <returns></returns>
        [HttpPost("breakpoint/continuingly")]
        public async Task<dynamic> DownloadFileByPath(string storagePath)
        {
            string year = storagePath.Split('/').First();
            if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
            string filePath = FileVariable.OrganizationFilePath + storagePath;
            if (!File.Exists(filePath)) throw YNTKException.Oh("客户端下载文件失败,文件不存在！");
            //先求出最后出现这个字符的下标
            int index = storagePath.LastIndexOf('/');
            //从下一个索引开始截取
            string saveFileName = filePath.Substring(index + 1);
            string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
            return await DownloadFileByType(filePath, fileDownloadName, 4096, true);
        }

        /// <summary>
        /// 获取下载文件链接
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        [HttpGet("download/{type}/{fileName}")]
        [NonAction]
        public dynamic DownloadUrl(string type, string fileName)
        {
            var url = _userManager.UserId + "|" + fileName + "|" + type;
            var encryptStr = DESCEncryption.Encrypt(url, "YNTK");
            return new { name = fileName, url = "/api/file/Download?encryption=" + encryptStr };
        }

        /// <summary>
        /// 下载文件链接
        /// </summary>
        [HttpGet("download")]
        [NonAction]
        public async Task<dynamic> DownloadFile([FromQuery] string encryption)
        {
            var decryptStr = DESCEncryption.Decrypt(encryption, "YNTK");
            var paramsList = decryptStr.Split("|").ToList();
            if (paramsList.Count > 0)
            {
                var fileName = paramsList.Count > 1 ? paramsList[1] : "";
                string type = paramsList.Count > 2 ? paramsList[2] : "";
                var filePath = Path.Combine(GetPathByType(type), fileName.Replace("@", "."));
                var fileDownloadName = fileName.Replace(GetPathByType(type), "");
                return await DownloadFileByType(filePath, fileDownloadName);
            }
            else
            {
                throw YNTKException.Oh(ErrorCode.D8000);
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpDelete("delete/file")]
        public void DeleteFile([FromBody] string storagePath)
        {
            try
            {
                if(string.IsNullOrWhiteSpace(storagePath)) throw YNTKException.Oh(ErrorCode.D1807);
                string year = storagePath.Split('/').First();
                if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
                string filePath = FileVariable.OrganizationFilePath + storagePath;
                if (!File.Exists(filePath)) throw YNTKException.Oh(ErrorCode.D8000);
                //先求出最后出现这个字符的下标
                int index = storagePath.LastIndexOf('/');
                //从下一个索引开始截取
                string saveFileName = filePath.Substring(index + 1);
                string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
                string extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
                if (File.Exists(filePath)) File.Delete(filePath);
                if (AllowImageType(extension.Split('.')[1]))
                {
                    //删除图片缩略图
                    string thumbnailRoot = Path.GetDirectoryName(filePath);
                    string fileName = Path.GetFileName(filePath);
                    string thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
                    if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                }
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh($"文件存储服务删除文件发生异常：{ex.Message},文件存储路径：{storagePath}");
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpPost("delete/bypath")]
        public void DeleteFileByPath( string storagePath)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(storagePath)) throw YNTKException.Oh(ErrorCode.D1807);
                string year = storagePath.Split('/').First();
                if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
                string filePath = FileVariable.OrganizationFilePath + storagePath;
                if (!File.Exists(filePath)) throw YNTKException.Oh(ErrorCode.D8000);
                //先求出最后出现这个字符的下标
                int index = storagePath.LastIndexOf('/');
                //从下一个索引开始截取
                string saveFileName = filePath.Substring(index + 1);
                string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
                string extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
                if (File.Exists(filePath)) File.Delete(filePath);
                if (AllowImageType(extension.Split('.')[1]))
                {
                    //删除图片缩略图
                    string thumbnailRoot = Path.GetDirectoryName(filePath);
                    string fileName = Path.GetFileName(filePath);
                    string thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
                    if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                }
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh($"文件存储服务删除文件发生异常：{ex.Message},文件存储路径：{storagePath}");
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [NonAction]
        public void DeleteSingleFile(string storagePath)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(storagePath)) return;
                string year = storagePath.Split('/').First();
                if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
                string filePath = FileVariable.OrganizationFilePath + storagePath;
                //先求出最后出现这个字符的下标
                int index = storagePath.LastIndexOf('/');
                //从下一个索引开始截取
                string saveFileName = filePath.Substring(index + 1);
                string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
                string extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                    if (AllowImageType(extension.Split('.')[1]))
                    {
                        //删除图片缩略图
                        string thumbnailRoot = Path.GetDirectoryName(filePath);
                        string fileName = Path.GetFileName(filePath);
                        string thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
                        if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                    }
                }               
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"文件存储服务删除文件发生异常：{ex.Message},文件存储路径：{storagePath}");
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="paths">文件存储路径集合</param>
        [NonAction]
        public void DeleteFileByPaths(List<string> paths)
        {
            string storagePath = "";
            try
            {
                if (!paths.Any()) return;                
                string root = FileVariable.OrganizationFilePath;
                string filePath = "";
                int index = 0;
                string saveFileName = "";
                string fileDownloadName = "";
                string extension = "";
                string thumbnailRoot = "";
                string fileName = "";
                string thumbnailPath = "";
                string year = "";
                paths.ForEach(path =>
                {
                    storagePath = path;
                    year = storagePath.Split('/').First();
                    if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
                    filePath = root + storagePath;
                    //先求出最后出现这个字符的下标
                    index = storagePath.LastIndexOf('/');
                    //从下一个索引开始截取
                    saveFileName = filePath.Substring(index + 1);
                    fileDownloadName = saveFileName.Split("{@yntk@}")[1];
                    extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
                    if (File.Exists(filePath))
                    {
                        File.Delete(filePath);
                        if (AllowImageType(extension.Split('.')[1]))
                        {
                            //删除图片缩略图
                            thumbnailRoot = Path.GetDirectoryName(filePath);
                            fileName = Path.GetFileName(filePath);
                            thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
                            if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                        }
                    }
                });
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"文件存储服务删除文件发生异常：{ex.Message},文件存储路径：{storagePath}");
            }
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="files">文件集合</param>
        /// <returns></returns>
        [NonAction]
        public void DeleteFiles(List<FileListDto> files)
        {
            try
            {
                if (files != null && files.Count > 0)
                {
                    string filePath = "";//文件路径
                    string thumbnailPath = "";//缩略图路径
                    string thumbnailRoot = "";
                    string fileName = "";
                    string year = "";
                    files.ForEach(file =>
                    {
                        year = file.fileDirectory.Split('/').First();
                        if (year.Length == 1) file.fileDirectory = $"{DateTime.Now.Year}/{file.fileDirectory}";
                        filePath = FileVariable.OrganizationFilePath + file.fileDirectory + file.saveFileName;
                        if (File.Exists(filePath)) File.Delete(filePath);
                        if (AllowImageType(file.extension.Split('.')[1]))
                        {
                            //删除图片缩略图
                            thumbnailRoot = Path.GetDirectoryName(filePath);
                            fileName = Path.GetFileName(filePath);
                            thumbnailPath = $"{thumbnailRoot}/thumbnail_{fileName}";
                            if (File.Exists(thumbnailPath)) File.Delete(thumbnailPath);
                        }
                    });
                }
            }
            catch (Exception)
            {
                throw YNTKException.Oh(ErrorCode.D1805);
            }
        }

        /// <summary>
        /// 根据文件名获取ContentType
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <returns></returns>
        [HttpPost("contenttype")]
        public dynamic GetContentType(string fileName)
        {
            string contentType= Shipeng.Util.MimeMapping.GetMimeMapping(fileName);
            return contentType;
        }

        /// <summary>
        /// 获取图片base64
        /// </summary>
        /// <param name="storagePath">文件存储路径,存储目录/存储文件名</param>
        /// <returns></returns>
        [HttpPost("image/base64")]
        public string GetImageBase64(string storagePath)
        {
            string baser64 = "";
            if (string.IsNullOrWhiteSpace(storagePath)) return baser64;
            string year = storagePath.Split('/').First();
            if (year.Length == 1) storagePath = $"{DateTime.Now.Year}/{storagePath}";
            string filePath = FileVariable.OrganizationFilePath + storagePath;
            if (!File.Exists(filePath)) return baser64;
            //new FileStream(filePath, FileMode.Open)以只读共享的方式打开的，
            //但若此文件已被一个拥有写权限的进程打开的话，就无法读取了
            //设置文件共享方式为读写，FileShare.ReadWrite
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                var byteArray = new byte[fs.Length];
                fs.Read(byteArray, 0, byteArray.Length);
                baser64 = "data:image/jpeg;base64," + Convert.ToBase64String(byteArray);
                fs.Close();
                fs.Dispose();
            }
            return baser64;
        }
        #endregion

        #region 超大视频流
        /// <summary>
        /// 大文件文件分片下载(超过100M的视频推荐使用此接口),(Item1.arraybuffer Item2.contentType)
        /// </summary>
        /// <param name="fileInfo">大文件分片下载请求模型</param>
        /// <returns></returns>
        [HttpPost("video/download")]
        public async Task<dynamic> FileDownload([FromBody] FileDownloadRequestModel fileInfo)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(fileInfo.storagePath)) throw YNTKException.Oh(ErrorCode.D1807);
                string filePath = FileVariable.OrganizationFilePath + fileInfo.storagePath;
                if (!File.Exists(filePath)) throw YNTKException.Oh(ErrorCode.D8000);
                //先求出最后出现这个字符的下标
                int index = fileInfo.storagePath.LastIndexOf('/');
                //从下一个索引开始截取
                string saveFileName = filePath.Substring(index + 1);
                string fileDownloadName = saveFileName.Split("{@yntk@}")[1];
                string extension = Path.GetExtension(fileDownloadName);//获取文件格式，拓展名
                string contentType = Shipeng.Util.MimeMapping.GetMimeMapping(fileDownloadName);

                //开始准备分段下载
                using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite,
                    FileShare.ReadWrite, 4096, true))
                {
                    if(fs.Length<=0) throw YNTKException.Oh("文件尚未处理");
                    int shardSize = 1 * 1024 * 1024;//一次1M
                    int count = (int)(fs.Length / shardSize);
                    if ((fs.Length % shardSize) > 0) count += 1;
                    if (fileInfo.number > count - 1) throw YNTKException.Oh("无效的分片序号");
                    fs.Seek(fileInfo.number * shardSize, SeekOrigin.Begin);
                    if (fileInfo.number == count - 1)
                    {
                        //最后一片 = 总长 - (每次片段大小 * 已下载片段个数)
                        shardSize = (int)(fs.Length - (shardSize * fileInfo.number));
                    }
                    byte[] datas = new byte[shardSize];
                    await fs.ReadAsync(datas, 0, datas.Length);

                    return (datas,contentType);
                }
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("[")) throw YNTKException.Oh($"{ex.Message}");
                throw YNTKException.Oh($"大文件文件分片下载发生异常：{ex.Message}");
            }
        }
        #endregion

        #region 多种存储文件
        /// <summary>
        /// 根据存储类型上传文件
        /// </summary>
        /// <param name="file"></param>
        /// <param name="filePath"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        [NonAction]
        public async Task UploadFileByType(IFormFile file, string filePath, string fileName)
        {
            try
            {
                var bucketName = KeyVariable.BucketName;//存储桶名称
                var fileStoreType = KeyVariable.FileStoreType;//文件储存类型
                var uploadPath = Path.Combine(filePath, fileName);//文件上传路径
                var stream = file.OpenReadStream();
                switch (fileStoreType)
                {
                    case "minio"://Minio自建对象储存
                        await _oSSServiceFactory.Create().PutObjectAsync(bucketName, uploadPath, stream);
                        break;
                    case "aliyun-oss"://阿里云OSS
                        await _oSSServiceFactory.Create("aliyun").PutObjectAsync(bucketName, uploadPath, stream);
                        break;
                    case "tencent-cos"://腾讯云COS
                        await _oSSServiceFactory.Create("qcloud").PutObjectAsync(bucketName, uploadPath, stream);
                        break;
                    default://本地存储
                        if (!Directory.Exists(filePath))
                            Directory.CreateDirectory(filePath);
                        using (var stream4 = File.Create(uploadPath))
                        {
                            await file.CopyToAsync(stream4);
                        }
                        break;
                }
            }
            catch (Exception)
            {
                throw YNTKException.Oh(ErrorCode.D8001);
            }
        }

        /// <summary>
        /// 根据存储类型下载文件
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <param name="fileDownLoadName">下载文件名</param>
        /// <param name="bufferSize">缓存区大小,默认的缓冲区大小是4096</param>
        /// <param name="useAsync">使用异步I/O还是同步I/O</param>
        /// <returns></returns>
        [NonAction]
        public async Task<FileStreamResult> DownloadFileByType
            (string filePath, string fileDownLoadName,int bufferSize= 4096,bool useAsync = false)
        {
            try
            {
                var bucketName = KeyVariable.BucketName;//存储桶名称
                var fileStoreType = KeyVariable.FileStoreType;//文件储存类型
                switch (fileStoreType)
                {
                    case "minio"://Minio自建对象储存
                        var url1 = await _oSSServiceFactory.Create().PresignedGetObjectAsync(bucketName, filePath, 86400);
                        var stream1 = await url1.GetAsStreamAsync();
                        return new FileStreamResult(stream1, "application/octet-stream") { FileDownloadName = fileDownLoadName };
                    case "aliyun-oss"://阿里云OSS
                        var url2 = await _oSSServiceFactory.Create("Aliyun").PresignedGetObjectAsync(bucketName, filePath, 86400);
                        var stream2 = await url2.GetAsStreamAsync();
                        return new FileStreamResult(stream2, "application/octet-stream") { FileDownloadName = fileDownLoadName };
                    case "tencent-cos"://腾讯云COS
                        var url3 = await _oSSServiceFactory.Create("QCloud").PresignedGetObjectAsync(bucketName, filePath, 86400);
                        var stream3 = await url3.GetAsStreamAsync();
                        return new FileStreamResult(stream3, "application/octet-stream") { FileDownloadName = fileDownLoadName };
                    default:
                        //File.OpenRead(filePath).Close();
                        if(useAsync)
                        {
                            var fileStream = new FileStream(filePath, FileMode.Open,
                                FileAccess.ReadWrite, FileShare.ReadWrite, bufferSize, useAsync);
                            return new FileStreamResult(fileStream, "application/octet-stream") { FileDownloadName = fileDownLoadName };
                        }                       
                        else
                        {
                            return new FileStreamResult(new FileStream(filePath, FileMode.Open), "application/octet-stream") { FileDownloadName = fileDownLoadName };
                        }
                }
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh(ErrorCode.D8003);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task ReadAsync(Stream stream, CancellationToken cancellationToken)
        {
            using (cancellationToken.Register(stream.Dispose))
            {
                var buffer = new byte[1024];
                var read = 0;
                while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    //处理读数据

                }
                stream.Close();
                await stream.DisposeAsync();
            }
        }
        #endregion

        #region 生成图片缩略图
        /// <summary> 
        /// 生成缩略图:背景颜色可以设置
        /// </summary> 
        /// <param name="originalImagePath">源图路径（物理路径）</param> 
        /// <param name="thumbnailPath">缩略图路径（物理路径）</param> 
        /// <param name="width">缩略图宽度</param>
        /// <param name="height">缩略图高度</param>
        /// <param name="byColor">空白背景填充颜色</param>
        private static void MakeThumbnail(string originalImagePath, string thumbnailPath, int width, int height, string byColor)
        {
            //获取原始图片  
            Image originalImage = Image.FromFile(originalImagePath);
            //缩略图画布宽高  
            int towidth = width;
            int toheight = height;
            //原始图片写入画布坐标和宽高(用来设置裁减溢出部分)  
            int x = 0;
            int y = 0;
            int ow = originalImage.Width;
            int oh = originalImage.Height;
            //原始图片画布,设置写入缩略图画布坐标和宽高(用来原始图片整体宽高缩放)  
            int bg_x = 0;
            int bg_y = 0;
            int bg_w = towidth;
            int bg_h = toheight;
            //倍数变量  
            double multiple = 0;
            //获取宽长的或是高长与缩略图的倍数  
            if (originalImage.Width >= originalImage.Height)
            {
                multiple = (double)originalImage.Width / (double)width;
            }
            else
            {
                multiple = (double)originalImage.Height / (double)height;
            }
            //上传的图片的宽和高小等于缩略图  
            if (ow <= width && oh <= height)
            {
                //缩略图按原始宽高  
                bg_w = originalImage.Width;
                bg_h = originalImage.Height;
                //空白部分用背景色填充  
                bg_x = Convert.ToInt32(((double)towidth - (double)ow) / 2);
                bg_y = Convert.ToInt32(((double)toheight - (double)oh) / 2);
            }
            //上传的图片的宽和高大于缩略图  
            else
            {
                //宽高按比例缩放  
                bg_w = Convert.ToInt32((double)originalImage.Width / multiple);
                bg_h = Convert.ToInt32((double)originalImage.Height / multiple);
                //空白部分用背景色填充  
                bg_y = Convert.ToInt32(((double)height - (double)bg_h) / 2);
                bg_x = Convert.ToInt32(((double)width - (double)bg_w) / 2);
            }
            //新建一个bmp图片,并设置缩略图大小.  
            Image bitmap = new Bitmap(towidth, toheight);
            //新建一个画板  
            Graphics g = Graphics.FromImage(bitmap);
            //设置高质量插值法  
            g.InterpolationMode = InterpolationMode.HighQualityBilinear;
            //设置高质量,低速度呈现平滑程度  
            g.SmoothingMode = SmoothingMode.HighQuality;
            //清空画布并设置背景色  
            g.Clear(ColorTranslator.FromHtml(byColor));
            //在指定位置并且按指定大小绘制原图片的指定部分  
            //第一个System.Drawing.Rectangle是原图片的画布坐标和宽高,第二个是原图片写在画布上的坐标和宽高,最后一个参数是指定数值单位为像素  
            g.DrawImage(originalImage, new Rectangle(bg_x, bg_y, bg_w, bg_h),
                new Rectangle(x, y, ow, oh),GraphicsUnit.Pixel);
            try
            {
                //获取图片类型  
                string fileExtension =Path.GetExtension(originalImagePath).ToLower();
                //按原图片类型保存缩略图片,不按原格式图片会出现模糊,锯齿等问题.  
                switch (fileExtension)
                {
                    case ".gif": bitmap.Save(thumbnailPath, ImageFormat.Gif); break;
                    case ".jpg": bitmap.Save(thumbnailPath, ImageFormat.Jpeg); break;
                    case ".bmp": bitmap.Save(thumbnailPath, ImageFormat.Bmp); break;
                    case ".png": bitmap.Save(thumbnailPath, ImageFormat.Png); break;
                }
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"生成缩略图:背景颜色可以设置发生异常：{ex.Message}");
            }
            finally
            {
                originalImage.Dispose();
                bitmap.Dispose();
                g.Dispose();
            }
        }

        /// <summary> 
        /// 生成缩略图：无填充背景
        /// </summary> 
        /// <param name="originalImagePath">源图路径（物理路径）</param> 
        /// <param name="thumbnailPath">缩略图路径（物理路径）</param> 
        /// <param name="width">缩略图宽度</param> 
        /// <param name="height">缩略图高度</param>    
        private static void MakeThumbnail2(string originalImagePath, string thumbnailPath, int width, int height)
        {
            //获取原始图片
            Image originalImage = Image.FromFile(originalImagePath);
            //缩略图画布宽高
            int towidth = width;//缩略图宽度
            int toheight = height;//缩略图高度
                                  //原始图片写入画布坐标和宽高(用来设置裁减溢出部分)
            int x = 0;
            int y = 0;
            int ow = originalImage.Width;//原图宽度
            int oh = originalImage.Height;//原图高度

            //先判断宽，宽大于指定宽度的话将宽设置为指定宽度，高度等比例缩放。
            if (originalImage.Width > width)
            {
                towidth = width;
                toheight = originalImage.Height * towidth / originalImage.Width;
            }
            else
            {
                towidth = originalImage.Width;
                toheight = originalImage.Height;
            }
            //再判断高，高大于指定高度的话将高设置为指定高度，宽度等比例缩放。
            if (toheight > height)
            {
                toheight = height;
                towidth = originalImage.Width * toheight / originalImage.Height;
            }

            //新建一个bmp图片 
            Image bitmap = new Bitmap(towidth, toheight);

            //新建一个画板 
            Graphics g = Graphics.FromImage(bitmap);

            //设置高质量插值法 
            g.InterpolationMode = InterpolationMode.High;

            //设置高质量,低速度呈现平滑程度 
            g.SmoothingMode = SmoothingMode.HighQuality;

            //清空画布并以透明背景色填充 
            g.Clear(Color.Transparent);

            //在指定位置并且按指定大小绘制原图片的指定部分
            g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight),
                new Rectangle(x, y, ow, oh),GraphicsUnit.Pixel);

            try
            {
                //获取图片类型
                string fileExtension = Path.GetExtension(originalImagePath).ToLower();
                //按原图片类型保存缩略图片,不按原格式图片会出现模糊,锯齿等问题.
                switch (fileExtension)
                {
                    case ".gif": bitmap.Save(thumbnailPath, ImageFormat.Gif); break;
                    case ".jpg": bitmap.Save(thumbnailPath, ImageFormat.Jpeg); break;
                    case ".bmp": bitmap.Save(thumbnailPath, ImageFormat.Bmp); break;
                    case ".png": bitmap.Save(thumbnailPath, ImageFormat.Png); break;
                }
            }
            catch (Exception ex)
            {
                throw YNTKException.Oh($"生成缩略图：无填充背景发生异常：{ex.Message}");
            }
            finally
            {
                originalImage.Dispose();
                bitmap.Dispose();
                g.Dispose();
            }
        }

        /// <summary>
        /// 等比缩放图片
        /// </summary>
        /// <param name="pathImageFrom">图片地址（服务器绝对路径）</param>
        /// <param name="maxSize">宽和高最大值</param>
        private void ChangePicSize(string pathImageFrom, int maxSize)
        {
            Image imageFrom = null;
            if (File.Exists(pathImageFrom))
            {
                imageFrom = Image.FromFile(pathImageFrom);
                // 源图宽度及高度   
                int imageFromWidth = imageFrom.Width;
                int imageFromHeight = imageFrom.Height;
                // 生成的缩略图实际宽度及高度  
                int bitmapWidth = imageFromWidth;
                int bitmapHeight = imageFromHeight;
                if (imageFromWidth > maxSize || imageFromHeight > maxSize)
                {
                    if (imageFromWidth > imageFromHeight)
                    {
                        bitmapWidth = maxSize;
                        bitmapHeight = imageFromHeight * maxSize / imageFromWidth;
                    }
                    else
                    {
                        bitmapWidth = imageFromWidth * maxSize / imageFromHeight;
                        bitmapHeight = maxSize;
                    }
                }
                // 创建画布   
                Bitmap bmp = new Bitmap(bitmapWidth, bitmapHeight);
                Graphics g = Graphics.FromImage(bmp);
                // 用白色清空   
                g.Clear(Color.White);
                // 指定高质量的双三次插值法。执行预筛选以确保高质量的收缩。此模式可产生质量最高的转换图像。
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                // 指定高质量、低速度呈现。   
                g.SmoothingMode = SmoothingMode.HighQuality;
                // 在指定位置并且按指定大小绘制指定的 Image 的指定部分。   
                g.DrawImage(imageFrom, new Rectangle(0, 0, bitmapWidth, bitmapHeight), 
                    new Rectangle(0, 0, imageFromWidth, imageFromHeight), GraphicsUnit.Pixel);

                imageFrom.Dispose();
                bmp.Save(pathImageFrom, ImageFormat.Jpeg);

                //释放资源     
                bmp.Dispose();
                g.Dispose();
            }
        }
        #endregion


        /// <summary>
        /// 根据类型获取文件存储路径
        /// </summary>
        /// <param name="type">文件类型</param>
        /// <returns></returns>
        [NonAction]
        public string GetPathByType(string type)
        {
            string path = "";
            if (App.User != null)
                path = App.User.FindFirstValue(ClaimConst.ORGANIZEID) +
                "/" + App.User.FindFirstValue(ClaimConst.MECHANISMID) + "/";
            switch (type)
            {
                case "userAvatar":
                    return FileVariable.UserAvatarFilePath + path;//用户头像存储路径
                //case "mail":
                //    return ConfigurationFileConst.EmailFilePath;
                case "IM":
                    return FileVariable.IMContentFilePath + path;//IM内容文件存储路径
                case "weixin":
                    return FileVariable.MPMaterialFilePath + path;//微信公众号资源存储路径
                case "workFlow":
                    return FileVariable.SystemFilePath + path;//系统文件存储路径
                case "annex":
                    return FileVariable.SystemFilePath + path;//系统文件存储路径
                case "annexpic":
                    return FileVariable.SystemFilePath + path;//系统文件存储路径
                case "document":
                    return FileVariable.DocumentFilePath + path;//文档管理存储路径
                //case "dataBackup":
                //    return ConfigurationFileConst.DataBackupFilePath;
                case "preview":
                    return FileVariable.DocumentPreviewFilePath + path;//文件在线预览存储PDF
                case "screenShot":
                case "banner":
                case "bg":
                case "border":
                case "source":
                    return FileVariable.BiVisualPath + path;//大屏图片路径
                case "template":
                    return FileVariable.TemplateFilePath + path;//模板路径
                case "codeGenerator":
                    return FileVariable.GenerateCodePath;//生成代码路径
                case "temporary":
                    return FileVariable.TemporaryFilePath + path;//临时文件存储路径
                default:
                    return FileVariable.OrganizationFilePath + path;//企业相关文件存储路径
            }
        }

        /// <summary>
        /// 允许文件类型
        /// </summary>
        /// <param name="fileExtension">文件后缀名</param>
        /// <param name="type">文件类型</param>
        /// <returns></returns>
        private bool AllowFileType(string fileExtension, string type)
        {
            var allowExtension = KeyVariable.AllowUploadFileType;
            if (type.Equals("weixin"))
            {
                allowExtension = KeyVariable.WeChatUploadFileType;
            }
            if (!allowExtension.Any()) return true;
            var isExist = allowExtension.Find(a => a == fileExtension.ToLower());
            if (!string.IsNullOrEmpty(isExist))
                return true;
            else
                return false;
        }

        /// <summary>
        /// 允许文件类型
        /// </summary>
        /// <param name="fileExtension">文件后缀名</param>
        /// <returns></returns>
        private bool AllowImageType(string fileExtension)
        {
            var allowExtension = KeyVariable.AllowImageType;
            var isExist = allowExtension.Find(a => a == fileExtension.ToLower());
            if (!string.IsNullOrEmpty(isExist))
                return true;
            else
                return false;
        }

        #region 导入导出

        /// <summary>
        /// 导出
        /// </summary>
        /// <param name="jsonStr"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        [NonAction]
        public dynamic Export(string jsonStr, string name)
        {
            var _filePath = GetPathByType("");
            var _fileName = name + ".Json";
            if (!Directory.Exists(_filePath))
                Directory.CreateDirectory(_filePath);
            var byteList = new UTF8Encoding(true).GetBytes(jsonStr.ToCharArray());
            FileHelper.CreateFile(_filePath + _fileName, byteList);
            var fileName = _userManager.UserId + "|" + _filePath + _fileName + "|json";
            var output = new
            {
                name = _fileName,
                url = "/api/file/Download?encryption=" + DESCEncryption.Encrypt(fileName, "YNTK")
            };
            return output;
        }

        /// <summary>
        /// 导入
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        [NonAction]
        public string Import(IFormFile file)
        {
            var stream = file.OpenReadStream();
            var byteList = new byte[file.Length];
            stream.Read(byteList, 0, (int)file.Length);
            stream.Position = 0;
            var sr = new StreamReader(stream, Encoding.Default);
            var json = sr.ReadToEnd();
            sr.Close();
            stream.Close();
            return json;
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="type"></param>
        /// <param name="file"></param>
        [NonAction]
        public void UploadFile(string type, IFormFile file)
        {
            var fileType = Path.GetExtension(file.FileName).Replace(".", "");
            if (!this.AllowFileType(fileType, type))
                throw YNTKException.Oh(ErrorCode.D1800);
            var _filePath = GetPathByType(type);
            var _fileName = file.FileName;
            if (!Directory.Exists(_filePath))
                Directory.CreateDirectory(_filePath);
            using (var stream = File.Create(Path.Combine(_filePath, _fileName)))
            {
                file.CopyTo(stream);
            }
        }
        #endregion
    }
}